home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Games / Tetris Light 1.0.3 / source / game.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-07-07  |  23.9 KB  |  949 lines  |  [TEXT/CWIE]

  1. /* ----------------------------------------------------------------------
  2. File: game.c
  3.  
  4. Purpose:    This screen interface module for the Tetris program.  It is
  5.             also where the playing field is stored.  This was placed
  6.             here to simplify things if we want to port Tetris to 
  7.             a character based platform, where we could just read and
  8.             write characters to screen memory and use it to store the
  9.             grid!
  10.             
  11. Tetris Light - a simple implementation of a Tetris game
  12. Copyright © 1993-1996 Hoylen Sue
  13.  
  14. Updated for CW9 by Paul Celestin
  15. Questions about this version should go to me at celestin@celestin.com
  16.  
  17. This program is free software; you can redistribute it and/or modify
  18. it under the terms of the GNU General Public License as published by
  19. the Free Software Foundation; either version 2 of the License, or
  20. (at your option) any later version.
  21.  
  22. This program is distributed in the hope that it will be useful,
  23. but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  25. GNU General Public License for more details.
  26.  
  27. You should have received a copy of the GNU General Public License
  28. along with this program; see the file COPYING.  If not, write to the
  29. Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  30. ---------------------------------------------------------------------- */
  31.  
  32. #include "local.h"
  33.  
  34. #include <Sound.h>
  35.  
  36. #include "alert.h"
  37. #include "controls.h"
  38. #include "dialutil.h"
  39. #include "env.h"
  40. #include "game.h"
  41. #include "highscore.h"
  42. #include "pstring.h"
  43. #include "resources.h"
  44. #include "tetris.h"
  45. #include "windows.h"
  46.  
  47. /*--------------------------------------------------------------------*/
  48.  
  49. /* Coordinates of where to draw things (in pixels) */
  50.  
  51. #define FIELD_TOP    3
  52. #define FIELD_LEFT    3
  53. #define CELL_SIZE    16        /* Size of cell */
  54.  
  55. #define SCORE_VALUE_LEFT    170
  56. #define SCORE_VALUE_TOP        36
  57.  
  58. #define SCORE_TITLE_LEFT    170
  59. #define SCORE_TITLE_TOP        20
  60.  
  61. #define GAME_OVER_X            170
  62. #define GAME_OVER_Y            150
  63.  
  64. /* The following two are in cell units */
  65.  
  66. #define NEXT_PIECE_OFFSET_X    (NUMBER_COLS + 1)
  67. #define NEXT_PIECE_OFFSET_Y    3
  68.  
  69. /*--------------------------------------------------------------------*/
  70.  
  71. /* This macro symbol, if defined, compiles the code to draw using
  72.    primitive colours.  This will work on all Macs, but can be left
  73.    out to increase drawing efficiency. */
  74.    
  75. #define COLOUR_GRAPHICS 1
  76.  
  77. /*--------------------------------------------------------------------*/
  78.  
  79. /* Drawing and sound resources */
  80.  
  81. static Handle hit_snd = 0;    /* if null, use SysBeep */
  82. static Handle end_snd = 0;    /* if null, use SysBeep */
  83.  
  84. static unsigned char *score_str;
  85. static unsigned char *game_over_str;
  86.  
  87. static unsigned char *pause_str;
  88. static unsigned char *continue_str;
  89.  
  90. static Pattern cell_patterns[NUMBER_BLOCK_TYPES];
  91.  
  92. /*--------------------------------------------------------------------*/
  93.  
  94. /* The playing field and next block records */
  95.  
  96. static Rect field_rect = {
  97.     FIELD_TOP, FIELD_LEFT, FIELD_TOP + CELL_SIZE * NUMBER_ROWS, 
  98.     FIELD_LEFT + CELL_SIZE * NUMBER_COLS
  99. };
  100.  
  101. static unsigned char field[NUMBER_COLS][NUMBER_ROWS];
  102. static unsigned char next_block[MAX_BLOCK_SIZE][MAX_BLOCK_SIZE];
  103.  
  104. /*--------------------------------------------------------------------*/
  105.  
  106. static Boolean game_running;
  107. static Boolean game_paused;
  108. static Boolean game_window_active;    /* Records if game window is active */
  109.  
  110. static WindowPtr game_wind;
  111. static ControlHandle game_pause_ctrlh;
  112.  
  113. static RgnHandle rgn1;
  114. static RgnHandle rgn2;
  115. static RgnHandle rgn_top_row;
  116.  
  117. /*--------------------------------------------------------------------*/
  118.  
  119. /* Game file based saving and restoring routines */
  120.  
  121. static Boolean saved_file_known = FALSE;
  122. static Str255 saved_name;
  123. static INTEGER saved_vref;
  124.  
  125. struct Save_header {
  126.     unsigned long magic;
  127.     struct Tetris_state state;
  128. };
  129.  
  130. #define SAVE_MAGIC_NUMBER    ('TLsv' | 0x80808080)
  131.  
  132. /* These locations are based on centering the dialog in the classic
  133.    sized screen.  Dialog dimensions are based on those in Inside
  134.    Macintosh I. */
  135.  
  136. static const Point SFGetFile_where = {82, 103};
  137. static const Point SFPutFile_where = {104, 119};
  138.  
  139. /*--------------------------------------------------------------------*/
  140.  
  141. /* Local prototypes */
  142.  
  143. static void game_mouseDown(Point);
  144. static void game_key(unsigned char code, unsigned char ascii);
  145. static void game_update(void);
  146. static void game_activate(void);
  147. static void game_deactivate(void);
  148.  
  149. /*--------------------------------------------------------------------*/
  150.  
  151. /* Window dispatch table for game window */
  152.  
  153. static Wind_table game_dispatch_table = {
  154.     game_mouseDown,
  155.     game_key,
  156.     game_update,
  157.     game_activate,
  158.     game_deactivate
  159. };
  160.  
  161. /*--------------------------------------------------------------------*/
  162.  
  163. static void load_string(unsigned char **str, INTEGER resid)
  164. /* Tries to load the string resource with the given `resid' and sets
  165.    the pointer *str to it.  If it cannot be found, the pointer is set
  166.    to a default string.  Used by `game_init' to simplify code. */
  167. {
  168.     static unsigned char *default_string = "\p?";
  169.     
  170.     register StringHandle hand = GetString(resid);
  171.     if (hand != 0) {
  172.         HLock((Handle)hand);
  173.         *str = *hand;
  174.     } else
  175.         *str = default_string;
  176. }
  177.  
  178. /*--------------------------------------------------------------------*/
  179.  
  180. Boolean game_init(void)
  181. /* Initialization routine for game window.  Must be called at the
  182.    beginning of the program. Returns TRUE if it failed. */
  183. {
  184.     register int p;
  185.     Rect top_row;
  186.     
  187.     /* Load block patterns and strings */
  188.     
  189.     for (p = 0; p < NUMBER_BLOCK_TYPES; p ++)
  190.         GetIndPattern(cell_patterns + p, PATTERN_ID, p + 1);
  191.     
  192.     load_string(&score_str, SCORE_STR_ID);
  193.     load_string(&game_over_str, GAME_OVER_STR_ID);
  194.     load_string(&pause_str, PAUSE_STR_ID);
  195.     load_string(&continue_str, CONTINUE_STR_ID);
  196.  
  197.     /* Load sounds */
  198.     
  199.     hit_snd = GetResource('snd ', HIT_SND_ID);
  200.     if (hit_snd != 0)
  201.         HLock(hit_snd);
  202.     end_snd = GetResource('snd ', END_SND_ID);
  203.     if (end_snd != 0)
  204.         HLock(end_snd);
  205.     
  206.     /* Set up drawing variables */
  207.     
  208.     rgn1 = NewRgn();
  209.     rgn2 = NewRgn();
  210.     rgn_top_row = NewRgn();
  211.     top_row = field_rect;
  212.     top_row.bottom = top_row.top + CELL_SIZE;
  213.     RectRgn(rgn_top_row, &top_row);
  214.     
  215.     /* Create and setup window and its button */
  216.     
  217.     game_wind = GetNewWindow(GAME_WINDOW_ID, NIL, (WindowPtr) -1);
  218.     if (game_wind == 0)
  219.         return TRUE;
  220.     SetWRefCon(game_wind, (LONGINT) &game_dispatch_table);
  221.     game_window_active = FALSE;
  222.     
  223.     game_pause_ctrlh = GetNewControl(GAME_PAUSE_CNTL_ID, game_wind);
  224.     if (game_pause_ctrlh == 0)
  225.         return TRUE;
  226.     
  227.     /* Set up */
  228.     
  229.     SetPort(game_wind);
  230.     TextFont(0); /* System font */
  231.     game_running = FALSE;
  232.     game_paused = FALSE;
  233.     
  234.     return FALSE; /* Success */
  235.  
  236. /*--------------------------------------------------------------------*/
  237.  
  238. void game_begin(void)
  239. /* Show the game window.  This is a separate routine so that we can
  240.    optionally load the location information from the preferences file
  241.    before showing the window. */
  242. {
  243.     ShowWindow(game_wind);
  244. }
  245.  
  246. /*--------------------------------------------------------------------*/
  247.  
  248. void game_term(void)
  249. /* To be called at the end of the program. */
  250. {
  251.     CloseRgn(rgn1);
  252.     CloseRgn(rgn2);
  253.     CloseRgn(rgn_top_row);
  254.     DisposeWindow(game_wind);    /* Controls automatically deleted */
  255. }
  256.  
  257. /*--------------------------------------------------------------------*/
  258.  
  259. static saved_file_set(unsigned char *name, INTEGER vref)
  260. {
  261.     if (name) {
  262.         pstrcpy(saved_name, name);
  263.         saved_vref = vref;
  264.         saved_file_known = TRUE;
  265.     } else {
  266.         pstrcpy(saved_name, "\pUntitled");
  267.         saved_file_known = FALSE;
  268.     }
  269.     
  270.     SetWTitle(game_wind, saved_name);
  271. }
  272.  
  273. /*====================================================================*/
  274.  
  275. /* Internal drawing routines */
  276.  
  277. static void draw_game_over(void)
  278. /* If the game is over, the game over message is drawn, otherwise the
  279.    location where that string goes is erased.  This routine is used to
  280.    both draw and erase this message. */
  281. {
  282.     if (! game_running) {
  283.         MoveTo(GAME_OVER_X, GAME_OVER_Y);
  284.         DrawString(game_over_str);
  285.     } else {
  286.         /* Determine where that string will be drawn and erase it. */
  287.         Rect r;
  288.         FontInfo fi;
  289.         
  290.         GetFontInfo(&fi);
  291.         
  292.         r.left = GAME_OVER_X;
  293.         r.right = GAME_OVER_X + StringWidth(game_over_str);
  294.         r.top = GAME_OVER_Y - fi.ascent;
  295.         r.bottom = GAME_OVER_Y + fi.descent;
  296.         
  297.         EraseRect(&r);
  298.     }
  299. }
  300.  
  301. /*--------------------------------------------------------------------*/
  302.  
  303. static void draw_score(void)
  304. /* Draws the value of the score in the game window. */
  305. {
  306.     Str255 score_string;
  307.     Rect    bound;
  308.     
  309.     bound.left = SCORE_VALUE_LEFT;
  310.     bound.top = SCORE_VALUE_TOP - 10;
  311.     bound.right = SCORE_VALUE_LEFT + 50;
  312.     bound.bottom = SCORE_VALUE_TOP + 4;
  313.     
  314.     SetPort(game_wind);
  315.  
  316.     EraseRect(&bound);
  317.     NumToString(tetris_score(), score_string);
  318.     MoveTo(SCORE_VALUE_LEFT, SCORE_VALUE_TOP);
  319.     DrawString(score_string);
  320. }
  321.  
  322. /*--------------------------------------------------------------------*/
  323.  
  324. static void draw_cell(int x, int y, unsigned char pattern)
  325. /* Internal routine to draw a cell on the screen display. Assumes
  326.    that the drawing port has already been set up.  If the pattern is
  327.    the empty cell (pattern == 0), the cell is erased. */
  328. {
  329.     Rect cell;
  330.     
  331.     cell.left = x * CELL_SIZE + FIELD_LEFT;
  332.     cell.top = y * CELL_SIZE + FIELD_TOP;
  333.     cell.right = cell.left + CELL_SIZE;
  334.     cell.bottom = cell.top + CELL_SIZE;
  335.         
  336.     if (pattern == 0)
  337.         EraseRect(&cell);
  338.     else {
  339. #ifdef COLOUR_GRAPHICS
  340.         ForeColor(greenColor);
  341. #endif
  342.  
  343.         FillRect(&cell, &cell_patterns[pattern - 1]);
  344.         FrameRect(&cell);
  345.  
  346. #ifdef COLOUR_GRAPHICS
  347.         ForeColor(blackColor);
  348. #endif
  349.     }
  350. }
  351.  
  352. /*====================================================================*/
  353.  
  354. /* Dispatch table routines */
  355.  
  356. static void game_mouseDown(Point where)
  357. /* Handler for mouseDown events in the game window. Determines if the
  358.    pause/continue control has been hit, and processes it appropriately. */
  359. {
  360.     ControlHandle ctrl;
  361.     INTEGER part;
  362.     
  363.     SetPort(game_wind);
  364.     GlobalToLocal(&where);
  365.     part = FindControl(where, game_wind, &ctrl);
  366.  
  367.     if (ctrl != 0 && TrackControl(ctrl, where, NIL) != 0) {
  368.         if (game_paused) {
  369.             tetris_pause(FALSE);
  370.             SetCTitle(game_pause_ctrlh, pause_str);
  371.             game_paused = FALSE;
  372.         } else {
  373.             tetris_pause(TRUE);
  374.             SetCTitle(game_pause_ctrlh, continue_str);
  375.             game_paused = TRUE;
  376.         }
  377.     }
  378. }
  379.  
  380. /*--------------------------------------------------------------------*/
  381.  
  382. static void game_key(unsigned char code, unsigned char ascii)
  383. /* Handler for key presses when the game window is in front.
  384.    Recognizes the four control keys and processes them appropriately. */
  385. {
  386.     if (code == ctrls.code[KEY_LEFT])
  387.         tetris_try_move(move_left);
  388.     else if (code == ctrls.code[KEY_ROT])
  389.         tetris_try_move(move_anticlockwise);
  390.     else if (code == ctrls.code[KEY_RIGHT])
  391.         tetris_try_move(move_right);
  392.     else if (code == ctrls.code[KEY_DROP])
  393.         tetris_try_move(move_drop);
  394.     else {
  395.         /* Ignore all other keys */
  396.     }
  397. }
  398.  
  399. /*--------------------------------------------------------------------*/
  400.  
  401. static void game_update(void)
  402. /* Handler for update events to the game window.  Redraws the entire
  403.    game window. */
  404. {
  405.     register int x, y;
  406.     Rect r = field_rect;
  407.  
  408.     SetPort(game_wind);
  409.     DrawControls(game_wind);    
  410.     InsetRect(&r, -1, -1);
  411. #ifdef COLOUR_GRAPHICS
  412.     ForeColor(blueColor);
  413. #endif
  414.     FrameRect(&r);            /* Border to the playing field */
  415. #ifdef COLOUR_GRAPHICS
  416.     ForeColor(blackColor);
  417. #endif
  418.  
  419.     /* Show the score */
  420.     
  421.     MoveTo(SCORE_TITLE_LEFT, SCORE_TITLE_TOP);
  422.     DrawString(score_str);
  423.     draw_score();
  424.     
  425.     /* Field contents (draw from bottom up to look nice) */
  426.     
  427.     for (y = NUMBER_ROWS - 1; y >= 0; y--)
  428.         for (x = 0; x < NUMBER_COLS; x++)
  429.             if (field[x][y] != 0)
  430.                 draw_cell(x, y, field[x][y]);
  431.                 
  432.     /* The next block */
  433.     
  434.     for (x = 0; x < 4; x++)
  435.         for (y = 0; y < 4; y++)
  436.             if (next_block[x][y])
  437.                 draw_cell(x + NEXT_PIECE_OFFSET_X, y + NEXT_PIECE_OFFSET_Y,
  438.                           next_block[x][y]);
  439.  
  440.     /* Game over text, if appropriate */
  441.     
  442.     draw_game_over();
  443. }
  444.  
  445. /*--------------------------------------------------------------------*/
  446.  
  447. static void game_activate(void)
  448. /* Handler for activate events to the game window.  Takes game out of
  449.    any pause mode that was automatically entered when it went behind
  450.    another window. */
  451. {
  452.     HiliteControl(game_pause_ctrlh, 0); /* Activate it */
  453.     if (game_running && !game_paused)
  454.         tetris_pause(FALSE);
  455.  
  456.     game_window_active = TRUE;
  457. }
  458.  
  459. /*--------------------------------------------------------------------*/
  460.  
  461. static void game_deactivate(void)
  462. /* Handler for deactivate events to the game window.  Automatically
  463.    pauses the game. */
  464. {
  465.     HiliteControl(game_pause_ctrlh, 255);
  466.     if (game_running && !game_paused)
  467.         tetris_pause(TRUE);
  468.  
  469.     game_window_active = FALSE;
  470. }
  471.  
  472. /*====================================================================*/
  473.  
  474. /* Support routines for the tetris module */
  475.  
  476. void game_del_row(int victim)
  477. /* Called by the tetris module to remove a complete row from the playing
  478.    field. It is here that sounds are played and the display and field
  479.    updated. */
  480. {
  481.     register int row, col;
  482.     Rect area;
  483.  
  484. #ifdef COLOUR_GRAPHICS
  485.     /* Draws the border of the completed row in red before playing the
  486.        sound.  Should be nice if you have a colour Mac. */
  487.        
  488.     ForeColor(redColor);
  489.  
  490.     area.top = FIELD_TOP + CELL_SIZE * victim;
  491.     area.left = FIELD_LEFT;
  492.     area.bottom = area.top + CELL_SIZE; 
  493.     area.right = FIELD_LEFT + CELL_SIZE * NUMBER_COLS;
  494.     FrameRect(&area);
  495.  
  496.     ForeColor(blackColor);
  497. #endif
  498.  
  499.     /* Play the sound */
  500.     
  501.     if (ctrls.sound_on)
  502.         if (hit_snd == 0 || SndPlay(NIL, (SndListHandle)hit_snd, FALSE) != noErr)
  503.             SysBeep(5);
  504.     /* Delete row from field array */
  505.         
  506.     for (row = victim - 1; row >= 0; row--)
  507.         for (col = 0; col < NUMBER_COLS; col++)
  508.             field[col][row + 1] = field[col][row];
  509.             
  510.     for (col = 0; col < NUMBER_COLS; col++)
  511.         field[col][0] = 0;
  512.     
  513.     /* Display this on the screen */
  514.     
  515.     area.top = FIELD_TOP;
  516.     area.left = FIELD_LEFT;
  517.     area.bottom = FIELD_TOP + CELL_SIZE * (victim + 1); 
  518.     area.right = FIELD_LEFT + CELL_SIZE * NUMBER_COLS;
  519.  
  520.     SetPort(game_wind);
  521.     ScrollRect(&area, 0, CELL_SIZE, rgn1);
  522.  
  523.     /* Need to redraw entire field if more than just the top becomes
  524.        invalid.  This is because we do not know if more rows will be
  525.        deleted after this, and hence we may be scrolling invalid
  526.        regions around.  We can't just invalidate this rgn1 because
  527.        the next scroll will move the bad pixels from under it. */
  528.     
  529.     DiffRgn(rgn1, rgn_top_row, rgn2);
  530.     if (! EmptyRgn(rgn2))
  531.         game_update();
  532. }
  533.  
  534. /*--------------------------------------------------------------------*/
  535.  
  536. void game_set(int x, int y, unsigned char pattern)
  537. /* Called by the tetris module to set a field element. If the cell is
  538.    valid, its value is stored and it is redrawn. */
  539. {
  540.     if (x < 0 || x >= NUMBER_COLS || y < 0 || y >= NUMBER_ROWS)
  541.         return;
  542.     
  543.     field[x][y] = pattern;
  544.     SetPort(game_wind);
  545.     draw_cell(x, y, pattern);
  546. }
  547.  
  548. /*--------------------------------------------------------------------*/
  549.  
  550. unsigned char game_get(int x, int y)
  551. /* Called by the tetris module to get the value of a field element.  If
  552.    the coordinates of the cell are invalid, 0 is returned. */
  553. {
  554.     if (x < 0 || x >= NUMBER_COLS || y < 0 || y >= NUMBER_ROWS)
  555.         return 0;
  556.     else
  557.         return field[x][y];
  558. }
  559.  
  560. /*--------------------------------------------------------------------*/
  561.  
  562. void game_score_changed(void)
  563. /* Called by the tetris module to indicate that the score has changed.
  564.    This function draws the new score on the screen. */
  565. {
  566.     draw_score();
  567. }
  568.  
  569. /*--------------------------------------------------------------------*/
  570.  
  571. void game_over(void)
  572. /* Called by the tetris module to indicate the game is over.  Plays
  573.    a sound and updates the display, trying to record the score in the
  574.    high score list. */
  575. {
  576.     if (ctrls.sound_on)
  577.         if (end_snd == 0 || SndPlay(NIL, (SndListHandle)end_snd, FALSE) != noErr)
  578.             SysBeep(5);
  579.     
  580.     game_running = FALSE;
  581.     SetPort(game_wind);
  582.     draw_game_over();
  583.     
  584.     HiliteControl(game_pause_ctrlh, 255); /* make inactive */
  585.     
  586.     highscore_add(tetris_score());
  587. }
  588.  
  589. /*--------------------------------------------------------------------*/
  590.  
  591. void describe_next_begin(void)
  592. /* Called by the tetris module to indicate that it is about to describe
  593.    the cells of the next block to be played (by calling the 
  594.    `describe_next_cell' below).  We clear the local record of the next
  595.    block. */
  596. {
  597.     register int x, y;
  598.     
  599.     SetPort(game_wind);
  600.  
  601.     for (x = 0; x < 4; x++)
  602.         for (y = 0; y < 4; y++)
  603.             next_block[x][y] = 0;
  604. }
  605.  
  606. /*--------------------------------------------------------------------*/
  607.  
  608. void describe_next_cell(int x, int y, unsigned char pattern)
  609. /* Used by the tetris module to describe the cells of the next block.
  610.    Will be called between calls to `describe_next_begin' and 
  611.    `describe_next_end'. */
  612. {
  613.     if (ctrls.show_next_piece)
  614.         next_block[x][y] = pattern;
  615. }
  616.  
  617. /*--------------------------------------------------------------------*/
  618.  
  619. void describe_next_end(void)
  620. /* Used by the tetris module to indicate that it is finished describing
  621.    the next block.  Invalidates the next block's display rectangular
  622.    area so that it will be updated. */
  623. {
  624.     Rect r;
  625.     
  626.     r.left = NEXT_PIECE_OFFSET_X * CELL_SIZE + FIELD_LEFT;
  627.     r.top = NEXT_PIECE_OFFSET_Y * CELL_SIZE + FIELD_TOP;
  628.     r.right = r.left + CELL_SIZE * MAX_BLOCK_SIZE;
  629.     r.bottom = r.top + CELL_SIZE * MAX_BLOCK_SIZE;
  630.     
  631.     SetPort(game_wind);
  632.     EraseRect(&r);
  633.     InvalRect(&r);
  634. }
  635.  
  636. /*====================================================================*/
  637.  
  638. /* Other interfaces to higher modules */
  639.  
  640. void game_periodic(void)
  641. /* This routine is called as often as possible.  It calls the tetris
  642.    peridic routine as often as possible when the game is played. */
  643. {
  644.     if (game_window_active && !game_paused && game_running)
  645.         tetris_periodic();
  646. }
  647.  
  648. /*--------------------------------------------------------------------*/
  649.  
  650. void game_new(void)
  651. /* This routine starts a new game, and resets the save file name and
  652.    window title. It does not ask the user whether to save the current
  653.    game or not - it is discarded. */
  654. {
  655.     register int x, y;
  656.     
  657.     /* Clears the field and next block to be empty */
  658.     
  659.     for (y = 0; y < NUMBER_ROWS; y++)
  660.         for (x = 0; x < NUMBER_COLS; x++)
  661.             field[x][y] = 0;
  662.         
  663.     /* Reset the pause push button */
  664.     
  665.     if (game_window_active)
  666.         HiliteControl(game_pause_ctrlh, 0);
  667.     
  668.     /* Start up the game */
  669.     
  670.     tetris_start(0);
  671.     game_running = TRUE;
  672.     game_paused = FALSE;
  673.     SetCTitle(game_pause_ctrlh, pause_str);
  674.     
  675.     /* Fix up the display */
  676.     
  677.     SetPort(game_wind);
  678.     EraseRect(&field_rect);
  679.     InvalRect(&field_rect);
  680.     draw_game_over();
  681.     draw_score();
  682.  
  683.     saved_file_set(0, 0);
  684. }
  685.  
  686. /*--------------------------------------------------------------------*/
  687.  
  688. void game_save_as(void)
  689. /* Save the current game under a new file, ignoring the file it might
  690.    have come from. */
  691. {
  692.     register INTEGER erc;
  693.     INTEGER file_ref;
  694.     SFReply reply;
  695.     
  696.     /* Get the file to save to */
  697.     
  698.     SFPutFile(SFPutFile_where, "\pSave game as:", "\pUntitled", NIL, &reply);
  699.     if (reply.good) {
  700.         saved_file_set(reply.fName, reply.vRefNum);
  701.         game_save();
  702.     }
  703. }
  704.  
  705. /*--------------------------------------------------------------------*/
  706.  
  707. void game_save(void)
  708. /* Creates and writes out the game information to the file specified
  709.    in `saved_reply'. If the file name is not already known, it calls
  710.    `game_save_as' to find it first. */
  711. {
  712.     OSErr erc;
  713.     LONGINT size;
  714.     INTEGER file_ref;
  715.     struct Save_header header;
  716.     
  717.     if (! saved_file_known) {
  718.         game_save_as();
  719.         return;
  720.     }
  721.         
  722.     /* Create the file, ignore error if it says it already exists */
  723.     
  724.     erc = Create(saved_name, saved_vref, CREATOR_SIGNATURE, SAVE_FILE_SIGNATURE);
  725.     if (erc != noErr && erc != dupFNErr) {
  726.         ParamText(saved_name, 0, 0, 0);
  727.         alert_erc(1, erc);
  728.         saved_file_set(0, 0);
  729.         return;
  730.     }
  731.  
  732.     erc = FSOpen(saved_name, saved_vref, &file_ref);
  733.     if (erc != noErr) {
  734.         ParamText(saved_name, 0, 0, 0);
  735.         alert_erc(2, erc);
  736.         saved_file_set(0, 0);
  737.         return;
  738.     }
  739.     
  740.     /* Fill in the header information */
  741.     
  742.     header.magic = SAVE_MAGIC_NUMBER;
  743.     tetris_state_get(&header.state);
  744.     
  745.     /* Write out the data */
  746.     
  747.     size = sizeof(header);
  748.     erc = FSWrite(file_ref, &size, &header);
  749.     if (erc != noErr || size != sizeof(header)) {
  750.         ParamText(saved_name, 0, 0, 0);
  751.         alert_erc(4, erc);
  752.         (void) FSClose(file_ref);
  753.         saved_file_set(0, 0);
  754.         return;
  755.     }
  756.  
  757.     size = sizeof(field);
  758.     erc = FSWrite(file_ref, &size, field);
  759.     if (erc != noErr || size != sizeof(field)) {
  760.         ParamText(saved_name, 0, 0, 0);
  761.         alert_erc(4, erc);
  762.         (void) FSClose(file_ref);
  763.         saved_file_set(0, 0);
  764.         return;
  765.     }
  766.     
  767.     /* Close file */
  768.     
  769.     erc = FSClose(file_ref);
  770.     if (erc != noErr) {
  771.         ParamText(saved_name, 0, 0, 0);
  772.         alert_erc(5, erc);
  773.         saved_file_set(0, 0);
  774.         return;
  775.     }
  776. }
  777.  
  778. /*--------------------------------------------------------------------*/
  779.  
  780. void game_open(void)
  781. /* Asks the user for a game save file.  If the user supplies one, it
  782.    is loaded up as the game to be played. */
  783. {
  784.     static SFTypeList sig_list = { SAVE_FILE_SIGNATURE };
  785.     SFReply reply;
  786.     
  787.     SFGetFile(SFGetFile_where, NIL, NIL, 1, sig_list, NIL, &reply);
  788.     if (reply.good)
  789.         game_load(reply.fName, reply.vRefNum);        
  790. }
  791.  
  792. /*--------------------------------------------------------------------*/
  793.  
  794. void game_load(Str255 fname, INTEGER vref)
  795. /* Loads the game from the given file. */
  796. {
  797.     OSErr erc;
  798.     INTEGER file_ref;
  799.     LONGINT size;
  800.     struct Save_header header;
  801.     unsigned char new_field[NUMBER_COLS][NUMBER_ROWS];
  802.     register int x, y;
  803.     
  804.     /* Open the save file */
  805.     
  806.     erc = FSOpen(fname, vref, &file_ref);
  807.     if (erc != noErr) {
  808.         ParamText(fname, 0, 0, 0);
  809.         alert_erc(2, erc);
  810.         return;
  811.     }
  812.     
  813.     /* Load the game */
  814.     
  815.     size = sizeof(header);
  816.     erc = FSRead(file_ref, &size, &header);
  817.     if (erc != noErr || size != sizeof(header)) {
  818.         ParamText(fname, 0, 0, 0);
  819.         alert_erc(3, erc);
  820.         (void) FSClose(file_ref);
  821.         return;
  822.     }
  823.     
  824.     size = sizeof(new_field);
  825.     erc = FSRead(file_ref, &size, new_field);
  826.     if (erc != noErr || size != sizeof(new_field)) {
  827.         ParamText(fname, 0, 0, 0);
  828.         alert_erc(3, erc);
  829.         return;
  830.     }
  831.     
  832.     /* Close the file */
  833.     
  834.     erc = FSClose(file_ref);
  835.     if (erc != noErr) {
  836.         ParamText(fname, 0, 0, 0);
  837.         alert_erc(5, erc);
  838.         return;
  839.     }
  840.  
  841.     /* Verify header */
  842.     
  843.     if (header.magic != SAVE_MAGIC_NUMBER) {
  844.         ParamText(fname, 0, 0, 0);
  845.         alert_caution(2);
  846.         return;
  847.     }
  848.     
  849.     /* Use field values, filtering out invalid values */
  850.     
  851.     for (x = 0; x < NUMBER_COLS; x++)
  852.         for (y = 0; y < NUMBER_ROWS; y++)
  853.             field[x][y] = new_field[x][y] % (NUMBER_BLOCK_TYPES + 1);
  854.     
  855.     /* Record this file as the saved file */
  856.     
  857.     saved_file_set(fname, vref);
  858.     
  859.     /* Start play */
  860.     
  861.     tetris_start(&header.state);
  862.     game_running = TRUE;
  863.     game_paused = FALSE;
  864.     SetCTitle(game_pause_ctrlh, pause_str);
  865.  
  866.     /* Update the screen */
  867.         
  868.     SetPort(game_wind);
  869.     draw_score();
  870.     draw_game_over();
  871.     EraseRect(&field_rect);
  872.     InvalRect(&field_rect);
  873. }
  874.  
  875. /*--------------------------------------------------------------------*/
  876.  
  877. OSErr game_save_location(INTEGER pref_file)
  878. /* Saves the game controls and user settings to the given resource
  879.    file. */
  880. {
  881.     OSErr erc;
  882.     LONGINT size;
  883.     Point **handle;
  884.     Rect r;
  885.     Handle myHandle;
  886.     
  887.     r = (**(*(WindowPeek)game_wind).contRgn).rgnBBox;
  888.     
  889.     handle = (Point **) GetResource(PREF_RSRC_TYPE, GAME_WPOS_PREF_ID);
  890.     if (handle != NIL && HomeResFile((Handle)handle) == pref_file) {
  891.         RmveResource((Handle)handle);
  892.         erc = ResError();
  893.         if (erc != noErr)
  894.             return erc;
  895.         
  896.         DisposHandle((Handle)handle);
  897.     }
  898.     
  899.     /* Create new resource */
  900.     
  901.     erc = PtrToHand(&r, &myHandle, sizeof(Rect));
  902.     if (erc != noErr)
  903.         return erc;
  904.         
  905.     AddResource(myHandle, PREF_RSRC_TYPE, GAME_WPOS_PREF_ID, "\p");
  906.     erc = ResError();
  907.     if (erc != noErr)
  908.         return erc;
  909.     
  910.     return noErr;
  911. }
  912.  
  913. /*--------------------------------------------------------------------*/
  914.  
  915. void game_load_location(void)
  916. /* Tries to read the window position information resource.  This is
  917.    found in the preference file, so it should be open at this stage.
  918.    If it is found and the location places the top right or the top left
  919.    corner in the desktop, it is moved to that location, otherwise it is
  920.    left alone. */
  921. {
  922.     register OSErr erc;
  923.     LONGINT size;
  924.     struct Rect **handle;
  925.     
  926.     handle = (struct Rect **) GetResource(PREF_RSRC_TYPE, GAME_WPOS_PREF_ID);
  927.     if (handle) {
  928.         Point ul, ur;
  929.         
  930.         ul.h = (*handle)->left;
  931.         ul.v = (*handle)->top;
  932.         ur.h = (*handle)->right;
  933.         ur.v = (*handle)->top;
  934.         
  935.         if (PtInRgn(ul, LMGetGrayRgn()) || PtInRgn(ur, LMGetGrayRgn()))
  936.             MoveWindow(game_wind, (*handle)->left, (*handle)->top, TRUE);
  937.     }
  938. }
  939.  
  940. /*--------------------------------------------------------------------*/
  941.  
  942. extern Boolean game_is_over(void)
  943. {
  944.     return (! game_running);
  945. }
  946.  
  947. /*--------------------------------------------------------------------*/
  948.